﻿using System;
using System.Collections.Generic;
using System.Linq;
using CommonBehaviors.Actions;
using Styx;
using Styx.CommonBot;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;
using Movement = HolySword.Helpers.MovementManager;
using Logger = HolySword.Helpers.LogManager;
using HolySword.Delegates;

namespace HolySword.Helpers
{
    public static class Spell
    {
        public static string LastSpellCastSuccess { get; set; }

        public static string LastSpellCastFailed { get; set; }

        public static string LastSpellAuraGained { get; set; }

        #region Category: Aura
        /// <summary>
        ///   Creates a behavior to return the auratimeleft, on specified WoWUnit
        /// </summary>
        /// <returns>seconds</returns>
        public static bool HasMyAuraTimeLeft(this WoWUnit unit, int id, int timeLeft)
        {
            return
                unit.GetAllAuras().Any(
                    a => a.SpellId == id && a.CreatorGuid == StyxWoW.Me.Guid && a.TimeLeft.TotalSeconds < timeLeft);
        }

        /// <summary>
        ///   Creates a behavior to return the auratimeleft, on specified WoWUnit
        /// </summary>
        /// <returns>seconds</returns>
        public static bool HasMyAuraTimeLeft(this WoWUnit unit, string aura, int timeLeft, bool special)
        {
            return
                unit.GetAllAuras().Any(
                    a =>
                    a.Name == aura && a.CreatorGuid == StyxWoW.Me.Guid && a.TimeLeft.TotalSeconds > timeLeft && special);
        }

        /// <summary>
        ///  Checks the aura count by the name on specified unit.
        /// </summary>
        /// <param name="unit"> The unit to check auras for. </param>
        /// <param name="aura"> The name of the aura in English. </param>
        /// <param name="stacks"> The stack count of the aura to return true. </param>
        /// <returns></returns>
        public static bool HasAura(this WoWUnit unit, int aura, int stacks)
        {
            return HasAura(unit, aura, stacks, null);
        }

        private static bool HasAllMyAuras(this WoWUnit unit, params int[] auras)
        {
            return auras.All(unit.HasMyAura);
        }

        public static bool HasMyAura(this WoWUnit unit, int aura)
        {
            return HasMyAura(unit, aura, 0);
        }

        public static bool HasMyAura(this WoWUnit unit, int aura, int stacks)
        {
            return HasAura(unit, aura, stacks, StyxWoW.Me);
        }

        private static bool HasAura(this WoWUnit unit, int aura, int stacks, WoWUnit creator)
        {
            return unit.GetAllAuras().Any(a => a.SpellId == aura && a.StackCount >= stacks &&
                                               (creator == null || a.CreatorGuid == creator.Guid));
        }

        private static bool HasAnyAura(this WoWUnit unit, params string[] auraNames)
        {
            var auras = unit.GetAllAuras();
            var hashes = new HashSet<string>(auraNames);
            return auras.Any(a => hashes.Contains(a.Name));
        }

        public static bool HasAnyAura(this WoWUnit unit, HashSet<int> auraIDs)
        {
            var auras = unit.GetAllAuras();
            return auras.Any(a => auraIDs.Contains(a.SpellId));
        }

        public static bool HasAuraWithMechanic(this WoWUnit unit, params WoWSpellMechanic[] mechanics)
        {
            WoWAuraCollection auras = unit.GetAllAuras();
            return auras.Any(a => mechanics.Contains(a.Spell.Mechanic));
        }

        public static TimeSpan GetAuraTimeLeft(this WoWUnit onUnit, string auraName, bool fromMyAura)
        {
            WoWAura wantedAura =
                onUnit.GetAllAuras().FirstOrDefault(
                    a => a.Name == auraName && (!fromMyAura || a.CreatorGuid == StyxWoW.Me.Guid));

            return wantedAura != null ? wantedAura.TimeLeft : TimeSpan.Zero;
        }

        #endregion

        #region Category: Wait

        public static Composite WaitForCast(bool faceDuring = true, bool allowLagTollerance = true)
        {
            return
                new Action(ret =>
                               {
                                   if (!StyxWoW.Me.IsCasting)
                                       return RunStatus.Failure;

                                   if (StyxWoW.Me.ChannelObjectGuid > 0)
                                       return RunStatus.Failure;

                                   uint latency = StyxWoW.WoWClient.Latency*2;
                                   TimeSpan castTimeLeft = StyxWoW.Me.CurrentCastTimeLeft;
                                   if (allowLagTollerance && castTimeLeft != TimeSpan.Zero &&
                                       StyxWoW.Me.CurrentCastTimeLeft.TotalMilliseconds < latency)
                                       return RunStatus.Failure;

                                   if (faceDuring && StyxWoW.Me.ChanneledCastingSpellId == 0)
                                       Movement.CreateFaceTargetBehavior();

                                   // return RunStatus.Running;
                                   return RunStatus.Success;
                               });
        }

        #endregion

        #region Catgory: CastSpell

        public static TimeSpan SpellCooldown(WoWSpell spell)
        {
            return SpellManager.HasSpell(spell) ? SpellManager.Spells[spell.Name].CooldownTimeLeft : TimeSpan.MaxValue;
        }

        public static bool SpellOnCooldown(WoWSpell spell)
        {
            return SpellManager.HasSpell(spell) && spell.Cooldown;
        }

        public static Composite BuffSelfSpell(WoWSpell wowspell, string log)
        {
            return CastSpell(wowspell, ret => StyxWoW.Me, ret => !StyxWoW.Me.HasMyAura(wowspell.Id), log);
        }

        public static Composite BuffSelfSpell(WoWSpell wowspell, SimpleBooleanDelegate requirements, string log)
        {
            return CastSpell(wowspell, ret => StyxWoW.Me, requirements, log);
        }

        public static Composite CastSpell(WoWSpell wowspell, string log)
        {
            return CastSpell(wowspell, ret => StyxWoW.Me.CurrentTarget, ret => true, log);
        }

        public static Composite CastSpell(WoWSpell wowspell, SimpleBooleanDelegate requirements, string log)
        {
            return CastSpell(wowspell, ret => StyxWoW.Me.CurrentTarget, requirements, log);
        }

        public static Composite CastSpell(WoWSpell wowspell, UnitSelectionDelegate onunit, string log)
        {
            return CastSpell(wowspell, onunit, ret => true, log);
        }

        public static Composite CastSpell(WoWSpell wowspell, UnitSelectionDelegate onunit,
                                          SimpleBooleanDelegate requirements, string log)
        {
            return
                new Decorator(ret =>
                              wowspell != null &&
                              onunit != null && requirements(ret)
                              && SpellManager.HasSpell(wowspell)
                              && CanCast(wowspell, onunit(ret)),
                              new Sequence(
                                  new Action(ret => SpellManager.Cast(wowspell, onunit(ret))),
                                  new Action(
                                      ret => Logger.CombatLog(string.Format("Casting {0} → {1}", wowspell.Name,
                                                                            log)))
                                  )
                    );
        }

        //Kudos to Wulf
        public static bool CanCast(WoWSpell name, WoWUnit target)
        {
            var canCast = false;
            var inRange = false;
            var minReqs = target != null;
            if (minReqs)
            {
                canCast = SpellManager.CanCast(name, target, false, false);
                if (canCast)
                {
                    // We're always in range of ourselves. So just ignore this bit if we're casting it on us
                    if (target.IsMe)
                    {
                        inRange = true;
                    }
                    else
                    {
                        WoWSpell spell;
                        if (SpellManager.Spells.TryGetValue(name.Name, out spell))
                        {
                            float minRange = spell.ActualMinRange(target);
                            float maxRange = spell.ActualMaxRange(target);
                            var targetDistance = DistanceToTargetBoundingBox(target);

                            // RangeId 1 is "Self Only". This should make life easier for people to use self-buffs, or stuff like Starfall where you cast it as a pseudo-buff.
                            if (spell.IsSelfOnlySpell)
                                inRange = true;
                            // RangeId 2 is melee range. Huzzah :)
                            else if (spell.IsMeleeSpell)
                                inRange = targetDistance < 4f;
                            else
                                inRange = targetDistance < maxRange &&
                                          targetDistance > (Math.Abs(minRange - 0) < 0.01 ? minRange : minRange + 3);
                        }
                    }
                }
            }

            return minReqs && canCast && inRange;
        }

        private static float ActualMaxRange(this WoWSpell spell, WoWUnit unit)
        {
            if (spell.MaxRange == 0)
                return 0;
            return unit != null ? spell.MaxRange + unit.CombatReach + 1f : spell.MaxRange;
        }

        private static float ActualMinRange(this WoWSpell spell, WoWUnit unit)
        {
            if (spell.MinRange == 0)
                return 0;
            return unit != null ? spell.MinRange + unit.CombatReach + 1.6666667f : spell.MinRange;
        }

        private static float DistanceToTargetBoundingBox(WoWUnit target)
        {
            if (target != null)
            {
                return (float)Math.Max(0f, target.Distance - target.BoundingRadius);
            }
            return 99999;
        }

        #endregion

        #region Category: CastOnGround

        public static Composite CastOnGround(string spellName, string log)
        {
            return CastOnGround(spellName, ret => true, log);
        }

        private static Composite CastOnGround(string spellName,
                                             SimpleBooleanDelegate requirement, string log)
        {
            return new Decorator(
                ret =>
                requirement(ret) && SpellManager.CanCast(spellName) && StyxWoW.Me.CurrentTarget.IsWithinMeleeRange,
                new Sequence(
                    new Action(ret => SpellManager.Cast(spellName)),
                    new WaitContinue(
                        1,
                        ret => StyxWoW.Me.CurrentPendingCursorSpell != null &&
                               StyxWoW.Me.CurrentPendingCursorSpell.Name == spellName,
                        new ActionAlwaysSucceed()),
                    new Action(ret => SpellManager.ClickRemoteLocation(StyxWoW.Me.CurrentTarget.Location)),
                    new Action(ctx => Logger.CombatLog(spellName + " " + log))
                    )
                );
        }

        #endregion
    }

    public static class Racials
    {
        public static readonly WoWSpell GiftoftheNaaru = WoWSpell.FromId(28880);
        public static readonly WoWSpell BloodFury = WoWSpell.FromId(20572);
        public static readonly WoWSpell Berserking = WoWSpell.FromId(26297);
        public static readonly WoWSpell EveryManForHimself = WoWSpell.FromId(59752);
    }

    public class Guild
    {
        public static WoWSpell MassResurrection = WoWSpell.FromId(83968);
        public static WoWSpell MobileBanking = WoWSpell.FromId(83958);
    }

    public class Professions
    {
        public static WoWSpell Lifeblood = WoWSpell.FromId(74497);
    }

    public static class Paladin
    {
        public static WoWSpell SpeedOfLight = WoWSpell.FromId(85499);
        public static readonly WoWSpell FistOfJustice = WoWSpell.FromId(105593);
        public static readonly WoWSpell Repentance = WoWSpell.FromId(20066);
        public static readonly WoWSpell EternalFlame = WoWSpell.FromId(114163);
        public static readonly WoWSpell SacredShield = WoWSpell.FromId(20925);
        public static readonly WoWSpell HandOfPurity = WoWSpell.FromId(114039);
        public static readonly WoWSpell HolyAvenger = WoWSpell.FromId(105809);
        public static WoWSpell SanctifiedWrath = WoWSpell.FromId(53376);
        public static readonly WoWSpell HolyPrism = WoWSpell.FromId(114165);
        public static readonly WoWSpell LightsHammer = WoWSpell.FromId(114158);
        public static readonly WoWSpell ExecutionSentence = WoWSpell.FromId(114157);
        public static WoWSpell Symbiosis = WoWSpell.FromId(110501);
        public static readonly WoWSpell AvengingWrath = WoWSpell.FromId(31884);
        public static WoWSpell BeaconofLight = WoWSpell.FromId(53563);
        public static readonly WoWSpell BlessingofKings = WoWSpell.FromId(20217);
        public static readonly WoWSpell BlessingofMight = WoWSpell.FromId(19740);
        public static WoWSpell Cleanse = WoWSpell.FromId(4987);
        public static readonly WoWSpell CrusaderStrike = WoWSpell.FromId(35395);
        public static WoWSpell Denounce = WoWSpell.FromId(2812);
        public static WoWSpell DevotionAura = WoWSpell.FromId(31821);
        public static WoWSpell DivineFavor = WoWSpell.FromId(31842);
        public static WoWSpell DivineLight = WoWSpell.FromId(82326);
        public static WoWSpell DivinePlea = WoWSpell.FromId(54428);
        public static readonly WoWSpell DivineProtection = WoWSpell.FromId(498);
        public static readonly WoWSpell DivineShield = WoWSpell.FromId(642);
        public static readonly WoWSpell FlashofLight = WoWSpell.FromId(19750);
        public static readonly WoWSpell GuardianofAncientKings = WoWSpell.FromId(86698);
        public static readonly WoWSpell HammerofJustice = WoWSpell.FromId(853);
        public static readonly WoWSpell HammerofWrath = WoWSpell.FromId(24275);
        public static WoWSpell HandofFreedom = WoWSpell.FromId(1044);
        public static WoWSpell HandofProtection = WoWSpell.FromId(1022);
        public static WoWSpell HandofSacrifice = WoWSpell.FromId(6940);
        public static readonly WoWSpell HandofSalvation = WoWSpell.FromId(1038);
        public static readonly WoWSpell HolyLight = WoWSpell.FromId(635);
        public static WoWSpell HolyRadiance = WoWSpell.FromId(82327);
        public static WoWSpell HolyShock = WoWSpell.FromId(20473);
        public static readonly WoWSpell Judgment = WoWSpell.FromId(20271);
        public static WoWSpell LayonHands = WoWSpell.FromId(633);
        public static WoWSpell LightofDawn = WoWSpell.FromId(85222);
        public static readonly WoWSpell Rebuke = WoWSpell.FromId(96231);
        public static WoWSpell Reckoning = WoWSpell.FromId(62124);
        public static WoWSpell Redemption = WoWSpell.FromId(7328);
        public static readonly WoWSpell RighteousFury = WoWSpell.FromId(25780);
        public static WoWSpell SealofInsight = WoWSpell.FromId(20165);
        public static readonly WoWSpell SealofRighteousness = WoWSpell.FromId(20154);
        public static readonly WoWSpell SealofTruth = WoWSpell.FromId(31801);
        public static readonly WoWSpell TurnEvil = WoWSpell.FromId(10326);
        public static readonly WoWSpell WordofGlory = WoWSpell.FromId(85673);
        public static readonly WoWSpell ArdentDefender = WoWSpell.FromId(31850);
        public static readonly WoWSpell AvengersShield = WoWSpell.FromId(31935);
        public static WoWSpell BlindingLight = WoWSpell.FromId(115750);
        public static readonly WoWSpell Consecration = WoWSpell.FromId(26573);
        public static readonly WoWSpell HammeroftheRighteous = WoWSpell.FromId(53595);
        public static readonly WoWSpell HolyWrath = WoWSpell.FromId(119072);
        public static readonly WoWSpell ShieldoftheRighteous = WoWSpell.FromId(53600);
        public static readonly WoWSpell DivineStorm = WoWSpell.FromId(53385);
        public static WoWSpell Emancipate = WoWSpell.FromId(121783);
        public static readonly WoWSpell Exorcism = WoWSpell.FromId(879);
        public static readonly WoWSpell Inquisition = WoWSpell.FromId(84963);
        public static WoWSpell SealofJustice = WoWSpell.FromId(20164);
        public static readonly WoWSpell TemplarsVerdict = WoWSpell.FromId(85256);
    }
}
